#include <unistd.h>
#include <sys/types.h>
#include <bolt.h>
#include <prot.h>
#include "pkt_queue.h"
#include "pkt_pool.h"

struct pkt_queue {
  int id;
  int use;
  uint32_t holds;
  int fd[2];
  el_handle_t handle;
  struct queue_stat stat;
  TAILQ_HEAD(pq_head, llbuf) pq_head;
};

static struct pkt_queue pkt_queue[PKT_QUEUE_MAX];


struct queue_stat *get_queue_stat(int id)
{
  if (!valid_pkt_queue_id(id)) 
    return NULL;
  struct pkt_queue *q = &pkt_queue[id];
  return (q->use ? &q->stat : NULL);
}


static void init_queue_stat(struct queue_stat *stat)
{
  memset(stat, 0, sizeof(struct queue_stat));
}

bool pkt_queue_push(int id, struct llbuf *ll)
{
  if (!valid_pkt_queue_id(id)) 
    return false;
  
  struct pkt_queue *q = &pkt_queue[id];
  if (q->use) {
    TAILQ_INSERT_TAIL(&q->pq_head, ll, entry);
    q->holds++;
    return true;
  } else
    return false;
}

struct llbuf *pkt_queue_pop(int id)
{
  if (!valid_pkt_queue_id(id)) 
    return NULL;
  struct pkt_queue *q = &pkt_queue[id];
  if (!q->use) 
    return NULL;
  struct llbuf *ll;
  if (!TAILQ_EMPTY(&q->pq_head)) {
    ll = TAILQ_FIRST(&q->pq_head);
    TAILQ_REMOVE(&q->pq_head, ll, entry);
    q->holds--;
  } else
    ll = NULL;
  return ll;
}


bool pkt_event_send(int id, struct llbuf *ll)
{
  if (!valid_pkt_queue_id(id)) 
    return false;
  struct pkt_queue *q = &pkt_queue[id];
  if (!q->use)
    return false;
  pkt_queue_push(id, ll);
  if (q->handle)
    write(q->fd[1], &q->holds, sizeof(q->holds));  
  return true;
}

struct llbuf * pkt_event_recv(int id)
{
  if (!valid_pkt_queue_id(id)) {
    LOG("\n");
    return NULL;
  }
  struct pkt_queue *q = &pkt_queue[id];
  if (!q->use) {
    LOG("\n");
    return NULL;
  }
  struct llbuf *ll = pkt_queue_pop(id);
  if (q->handle) {
    read(q->fd[0], &q->holds, sizeof(q->holds));  
  }
  return ll;
}

bool pkt_queue_bind(int id, struct el_events *events, void *userdata)
{
  if (!valid_pkt_queue_id(id)){
    return false;
  }
  struct pkt_queue *q = &pkt_queue[id];
  if (!q->use || q->handle != NULL) {
    return false;
  }
  if (pipe(q->fd) < 0) {
    return false;
  }
  set_nonblock(q->fd[0]);
  set_nonblock(q->fd[1]);
  q->handle = el_add(q->fd[0], events, userdata);
  return true;
}


bool pkt_queue_unbind(int id)
{
  if (!valid_pkt_queue_id(id))
    return false;
  struct pkt_queue *q = &pkt_queue[id];
  if (!q->use || !q->handle) 
    return false;
  close(q->fd[0]);
  close(q->fd[1]);
  q->fd[0] = q->fd[1] = -1;
  el_rm(q->handle);
  q->handle = 0;
  return true;
}

bool pkt_queue_create(int id)
{
  if (!valid_pkt_queue_id(id))
    return false;
  struct pkt_queue *q = &pkt_queue[id];
  if (q->use)
    return false;
  q->use = 1;
  q->holds = 0;
  q->fd[0] = q->fd[1] = -1;
  q->handle = NULL;
  init_queue_stat(&q->stat);
  return true;
}

bool pkt_queue_destroy(int id)
{
  if (!valid_pkt_queue_id(id))
    return false;
  struct pkt_queue *q = &pkt_queue[id];
  while (q->use) {
    struct llbuf *ll = pkt_queue_pop(id);
    if (!ll)
      break;
    pkt_pool_free(ll->pool, ll);
  }
  pkt_queue_unbind(id);
  q->use = 0;
  return true;
}

void pkt_queue_init(void)
{
  int i;
  for (i = 0; i < PKT_QUEUE_MAX; i++) {  
    struct pkt_queue *q = &pkt_queue[i];
    q->id = i;
    q->use = 0;
    q->holds = 0;
    TAILQ_INIT(&q->pq_head);
  }
}

void pkt_queue_deinit(void)
{
  int i;
  for (i = 0; i < PKT_QUEUE_MAX; i++) {  
    struct pkt_queue *q = &pkt_queue[i];
    if (q->use)
      pkt_queue_destroy(i);
  }
}

BOLT(pkt_queue_release, "packet queue <id> release")
{
  uint32_t id;
  const char *argz = pop_argz();
  if (!str2u32(argz, &id)) {
    printf ("invalid id\n");
    return;
  }
  if (pkt_queue_destroy(id))
    printf ("success\n");
  else
    printf ("failure\n");
}

BOLT(pkt_queue_create, "packet queue <id> create")
{
  uint32_t id;
  const char *argz = pop_argz();
  if (!str2u32(argz, &id)) {
    printf ("invalid id\n");
    return;
  }
  if (pkt_queue_create(id))
    printf ("success\n");
  else
    printf ("failure\n");
}

BOLT(pkt_queue_show, "packet queue show")
{
  int i;
  printf ("%2s %10s %10s %10s %10s %10s %10s %10s %10s\n", 
    "id", "holds", "arp", "ip", "icmp", "arp-req", "arp-reply", "icmp-req", "icmp-reply");  
  for (i = 0 ; i < PKT_QUEUE_MAX; i++) {
    struct pkt_queue *q = &pkt_queue[i];
    printf ("%2d %10d %10d %10d %10d %10d %10d %10d %10d\n", 
      i, q->holds, q->stat.arp, q->stat.ip,q->stat.icmp, q->stat.arp_req,
      q->stat.arp_reply,q->stat.icmp_req,q->stat.icmp_reply);
  }
}

BOLT_PROFILE(queue, "", queue_profile, desc)
{
  int i;
  for (i = 0; i < PKT_QUEUE_MAX; i++) {
    struct pkt_queue *q = &pkt_queue[i];
    if (q && q->use) {
      cmd_load_profile(desc, "packet queue %d create", i);
    }
  }
  cmd_load_profile(desc, "\n");
}
